/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cl_userCmd.cpp - builds an intended movement command to send to the server
//


#include "client.h"


/*
 ==================
 CL_SetFlags

 Sets the user command flags
 ==================
*/
static void CL_SetFlags (userCmd_t *userCmd){

	userCmd->flags = 0;

	// Let the game know if the player is running
	if (in_toggleRun->integerValue){
		if (in_speed.toggled ^ in_alwaysRun->integerValue)
			userCmd->flags |= UCF_RUNNING;
	}
	else {
		if (in_speed.active ^ in_alwaysRun->integerValue)
			userCmd->flags |= UCF_RUNNING;
	}

	// Let the game know if any key catcher is active
	if (Key_GetCatchers())
		userCmd->flags |= UCF_TALKING;

	// Let the game know if any key at all is currently pressed, even if it
	// isn't bound to anything
	if (Key_IsAnyDown() && !Key_GetCatchers())
		userCmd->flags |= UCF_ANYKEYDOWN;
}

/*
 ==================
 CL_SetButtonBits

 Sets the user command button bits
 ==================
*/
static void CL_SetButtonBits (userCmd_t *userCmd){

	int		i;

	userCmd->buttons = 0;

	// Send a button bit even if the key was pressed and released in less than
	// a frame
	for (i = 0; i < KB_MAX; i++){
		if (in_buttons[i].active || in_buttons[i].wasPressed)
			userCmd->buttons |= BIT(i);

		in_buttons[i].wasPressed = false;
	}

	// Handle key button toggles
	if (in_toggleZoom->integerValue){
		if (in_buttons[KB_ZOOM].toggled)
			userCmd->buttons |= BUTTON_ZOOM;
		else
			userCmd->buttons &= ~BUTTON_ZOOM;
	}
}

/*
 ==================
 CL_AdjustAngles

 Adjusts the local angles
 ==================
*/
static void CL_AdjustAngles (){

	float	speed;

	if (in_toggleRun->integerValue){
		if (in_speed.toggled)
			speed = MS2SEC(com_frameMsec) * in_angleSpeedKey->floatValue;
		else
			speed = MS2SEC(com_frameMsec);
	}
	else {
		if (in_speed.active)
			speed = MS2SEC(com_frameMsec) * in_angleSpeedKey->floatValue;
		else
			speed = MS2SEC(com_frameMsec);
	}

	cl.viewAngles.pitch -= speed * in_pitchSpeed->floatValue * IN_KeyButtonState(&in_lookUp);
	cl.viewAngles.pitch += speed * in_pitchSpeed->floatValue * IN_KeyButtonState(&in_lookDown);

	if (!in_strafe.active){
		cl.viewAngles.yaw -= speed * in_yawSpeed->floatValue * IN_KeyButtonState(&in_turnRight);
		cl.viewAngles.yaw += speed * in_yawSpeed->floatValue * IN_KeyButtonState(&in_turnLeft);
	}
}

/*
 ==================
 CL_KeyMove

 Sets the user command based on key states
 ==================
*/
static void CL_KeyMove (userCmd_t *userCmd){

	float	forwardMove = 0.0f;
	float	sideMove = 0.0f;
	float	upMove = 0.0f;

	forwardMove += in_forwardSpeed->floatValue * IN_KeyButtonState(&in_moveForward);
	forwardMove -= in_forwardSpeed->floatValue * IN_KeyButtonState(&in_moveBack);

	sideMove += in_sideSpeed->floatValue * IN_KeyButtonState(&in_moveRight);
	sideMove -= in_sideSpeed->floatValue * IN_KeyButtonState(&in_moveLeft);

	if (in_strafe.active){
		sideMove += in_sideSpeed->floatValue * IN_KeyButtonState(&in_turnRight);
		sideMove -= in_sideSpeed->floatValue * IN_KeyButtonState(&in_turnLeft);
	}

	upMove += in_upSpeed->floatValue * IN_KeyButtonState(&in_moveUp);
	upMove -= in_upSpeed->floatValue * IN_KeyButtonState(&in_moveDown);

	userCmd->forwardMove = glqMath::FloatToChar(forwardMove);
	userCmd->sideMove = glqMath::FloatToChar(sideMove);
	userCmd->upMove = glqMath::FloatToChar(upMove);
}

/*
 ==================
 CL_MouseMove

 Sets the user command based on mouse states
 ==================
*/
static void CL_MouseMove (userCmd_t *userCmd){

	float	rate, accelSensitivity;
	float	x, y;

	if (!in_mouse->integerValue)
		return;

	// Get smoothed mouse movement
	IN_MouseMove(m_smooth->integerValue, &x, &y);

	if (!x && !y)
		return;

	// Calculate acceleration and sensitivity
	rate = glqMath::Sqrt(x*x + y*y) / com_frameMsec;
	accelSensitivity = rate * m_accel->floatValue + m_sensitivity->floatValue;

	// Account for game sensitivity scale
	accelSensitivity *= cl.sensitivityScale;

	if (m_showRate->integerValue && rate)
		Com_Printf("%f : %f\n", rate, accelSensitivity);

	// Add mouse movement
	if (!accelSensitivity)
		return;

	x *= accelSensitivity;
	y *= accelSensitivity;

	if (in_strafe.active || !in_freeLook->integerValue){
		userCmd->forwardMove = glqMath::FloatToChar(userCmd->forwardMove - y * m_forward->floatValue);
		userCmd->sideMove = glqMath::FloatToChar(userCmd->sideMove + x * m_side->floatValue);
	}
	else {
		cl.viewAngles.pitch += y * m_pitch->floatValue;
		cl.viewAngles.yaw -= x * m_yaw->floatValue;
	}
}

/*
 ==================
 
 Sets the user command based on controller states
 ==================
*/
void CL_ControllerMove (userCmd_t *userCmd){

}

/*
 ==================
 CL_BuildUserCmd

 Builds a new user command for this frame
 ==================
*/
void CL_BuildUserCmd (){

	userCmd_t	*userCmd;
	glqAngles	viewAngles;

	if (cls.state < CS_PRIMED || clc.demoPlaying)
		return;

	cl.userCmdNumber++;
	userCmd = &cl.userCmds[cl.userCmdNumber & USERCMD_MASK];

	// Save current view angles
	viewAngles = cl.viewAngles;

	// If moving up, disallow moving down
	if (in_moveUp.active || in_moveUp.wasPressed){
		in_moveUp.wasPressed = false;

		Mem_Fill(&in_moveDown, 0, sizeof(keyButton_t));
	}

	// Set the time
	userCmd->time = cl.time;

	// Set the flags
	CL_SetFlags(userCmd);

	// Set the button bits
	CL_SetButtonBits(userCmd);

	// Keyboard angle adjustment
	CL_AdjustAngles();

	// Get movement from keyboard
	CL_KeyMove(userCmd);

	// Get movement from mouse
	CL_MouseMove(userCmd);

	// Get movement from controller
	CL_ControllerMove(userCmd);

	// If talking, disallow all other input
	if (userCmd->flags & UCF_TALKING){
		userCmd->flags = UCF_TALKING;
		userCmd->buttons = 0;

		userCmd->forwardMove = 0;
		userCmd->sideMove = 0;
		userCmd->upMove = 0;

		cl.viewAngles = viewAngles;
	}

	// Add game jitter angles
	cl.viewAngles += cl.jitterAngles;

	// Check to make sure pitch hasn't wrapped
	if (cl.viewAngles.pitch - viewAngles.pitch > 90.0f)
		cl.viewAngles.pitch = viewAngles.pitch + 90.0f;
	else if (viewAngles.pitch - cl.viewAngles.pitch > 90.0f)
		cl.viewAngles.pitch = viewAngles.pitch - 90.0f;

	// Set the angles
	userCmd->angles[0] = ANGLE2SHORT(cl.viewAngles[0]);
	userCmd->angles[1] = ANGLE2SHORT(cl.viewAngles[1]);
	userCmd->angles[2] = ANGLE2SHORT(cl.viewAngles[2]);
}